package org.ukiuni.pacifista;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ConnectException;
import java.net.SocketException;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.UUID;
import org.ukiuni.pacifista.util.FileUtil;
import org.ukiuni.pacifista.util.FileUtil.DirectoryPartAndFileName;
import org.ukiuni.pacifista.util.IOUtil;
import org.ukiuni.pacifista.util.StreamUtil.LinkedListInputStream;
import org.ukiuni.pacifista.util.StreamUtil.LinkedListOutputStream;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.ProxySOCKS5;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;
@SuppressWarnings("serial")
public class Remote {
private File baseDir;
private ProxySOCKS5 proxy;
private Session session;
private String encode = "UTF-8";
private String host;
private PrintStream out = System.out;
private String promptCharacter = "#";
private Runtime runtime;
public Remote(File baseDir, Runtime runtime) {
this.baseDir = baseDir;
this.runtime = runtime;
}
protected void setProxy(String host, int port) {
setProxy(host, port, null, null);
}
public PrintStream getOut() {
return out;
}
public void setOut(PrintStream out) {
this.out = out;
}
public void setProxy(String host, int port, String userName, String password) {
ProxySOCKS5 proxy = new ProxySOCKS5(host, port);
if (userName != null) {
proxy.setUserPasswd(userName, password);
}
this.proxy = proxy;
}
public int loadVersion() throws IOException {
return loadVersion("");
}
public int loadVersion(String name) throws IOException {
if ("".equals(name)) {
name = "/" + name;
}
String value = this.execute("cat /usr/local/pacifista" + name + "/version");
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
return 0;
}
}
public void sendVersion(int version) throws IOException {
sendVersion("", version);
}
public void sendVersion(String name, int version) throws IOException {
if ("".equals(name)) {
name = "/" + name;
}
this.execute("sudo mkdir /usr/local/pacifista" + name);
this.execute("sudo sh -c \"echo \'" + version + "\' > /usr/local/pacifista" + name + "/version\"");
}
public void connect(String host, int port, String account, File authFile) throws IOException {
this.connect(host, port, account, null, authFile);
}
public void connect(String host, int port, String account, String password) throws IOException {
this.connect(host, port, account, password, null);
}
public void connectWithAuthFile(String host, int port, String account, String authFilePath) throws IOException {
File authFile = FileUtil.pathToFile(baseDir, authFilePath);
this.connect(host, port, account, null, authFile);
}
public void connect(String host, int port, String account, final String password, File authFile) throws IOException {
this.host = host;
try {
Hashtable<String, String> config = new Hashtable<String, String>();
config.put("StrictHostKeyChecking", "no");
JSch.setConfig(config);
JSch jsch = new JSch();
this.session = jsch.getSession(account, host, port);
if (null != authFile) {
jsch.addIdentity(authFile.getAbsolutePath());
}
if (null != password) {
session.setUserInfo(new UserInfo() {
public void showMessage(String arg0) {
}
public boolean promptYesNo(String arg0) {
return false;
}
public boolean promptPassword(String arg0) {
return true;
}
public boolean promptPassphrase(String arg0) {
return false;
}
public String getPassword() {
return password;
}
public String getPassphrase() {
return password;
}
});
}
if (proxy != null) {
this.session.setProxy(proxy);
}
if (null != runtime.getEnv("socksProxyHost")) {
ProxySOCKS5 proxy = new ProxySOCKS5((String) runtime.getEnv("socksProxyHost"), Integer.valueOf((String) runtime.getEnv("socksProxyPort")));
if (null != runtime.getEnv("socksProxyUser")) {
proxy.setUserPasswd((String) runtime.getEnv("socksProxyUser"), (String) runtime.getEnv("socksProxyPassword"));
}
}
for (int i = 0; i < 10; i++) {
try {
this.session.connect();
break;
} catch (Exception e) {
if (i < 9 && (null != e.getCause() && (e.getCause() instanceof ConnectException || e.getCause() instanceof SocketException))) {
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
// Do nothing
}
} else {
throw new IOException(e);
}
}
}
} catch (JSchException e) {
throw new IOException(e);
}
}
public boolean isConnected() {
return null != this.session && this.session.isConnected();
}
public void sendDirectory(String localDirectoryPath, String remoteDirectory) throws IOException {
execute("mkdir " + remoteDirectory);
sendDirectory(FileUtil.pathToFile(baseDir, localDirectoryPath), remoteDirectory);
}
public void sendDirectory(File localDirectory, String remoteDirectory) throws IOException {
if (!localDirectory.isDirectory()) {
throw new IOException("localDirectory must a directory.");
}
String localDirectoryPath = localDirectory.getAbsolutePath();
if (!localDirectoryPath.endsWith(File.separator)) {
localDirectoryPath = localDirectoryPath + File.separator;
}
int localDirectoryPathLength = localDirectoryPath.length();
File[] files = localDirectory.listFiles();
for (File file : files) {
String relativePath = file.getAbsolutePath().substring(localDirectoryPathLength).replace(File.separator, "/");
if (file.isFile()) {
send(file, remoteDirectory, relativePath);
} else if (file.isDirectory()) {
String remoteChildDirectory = remoteDirectory + "/" + relativePath;
execute("mkdir " + remoteChildDirectory);
sendDirectory(file, remoteChildDirectory);
}
}
}
public void send(File file, String remotePath, String remoteFileName) throws IOException {
FileInputStream fileIn = null;
try {
fileIn = new FileInputStream(file);
send(fileIn, file.length(), remotePath, remoteFileName, null);
} finally {
if (null != fileIn) {
fileIn.close();
}
}
}
public void sendFile(String filePath, String remotePath, String remoteFileName) throws IOException {
File file = FileUtil.pathToFile(baseDir, filePath);
FileInputStream fileIn = null;
try {
fileIn = new FileInputStream(file);
send(fileIn, file.length(), remotePath, remoteFileName, null);
} finally {
if (null != fileIn) {
fileIn.close();
}
}
}
/**
* send string as file
*
* @param content
* send string
* @param remotePath
* remote path
* @param remoteFileName
* file name
* @throws IOException
*/
public void send(String content, String remotePath, String remoteFileName) throws IOException {
byte[] contentBytes = content.getBytes();
send(new ByteArrayInputStream(contentBytes), contentBytes.length, remotePath, remoteFileName, null);
}
/**
* send string as file
*
* @param content
* send string
* @param remotePath
* remote path
* @param remoteFileName
* file name
* @param mode
* mode
* @throws IOException
*/
public void send(String content, String remotePath, String remoteFileName, String mode) throws IOException {
byte[] contentBytes = content.getBytes();
send(new ByteArrayInputStream(contentBytes), contentBytes.length, remotePath, remoteFileName, mode);
}
public void send(InputStream fileIn, long fileSize, String remotePath, String remoteFileName, String mode) throws IOException {
String command = "scp -t " + remotePath;
Channel channel = null;
try {
channel = this.session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
OutputStream out = channel.getOutputStream();
InputStream in = channel.getInputStream();
channel.connect();
checkAck(in);
mode = null == mode ? "C0644" : mode;
command = mode + " " + fileSize + " ";
command += remoteFileName;
command += "\n";
out.write(command.getBytes());
out.flush();
checkAck(in);
IOUtil.copy(fileIn, out);
fileIn.close();
out.write(new byte[1]);
out.flush();
checkAck(in);
out.close();
channel.disconnect();
} catch (JSchException e) {
throw new IOException(e);
} finally {
if (channel != null) {
channel.disconnect();
}
}
}
public void recieve(String remoteFilePath, String outputDirectoryPath) throws IOException {
recieve(remoteFilePath, new File(outputDirectoryPath));
}
public void recieve(String remoteFilePath, File outputDirectory) throws IOException {
String command = "scp -f " + remoteFilePath;
Channel channel = null;
try {
channel = this.session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
OutputStream out = channel.getOutputStream();
InputStream in = channel.getInputStream();
channel.connect();
out.write(new byte[1]);
out.flush();
while (checkAck(in, 'C', false)) {
// start file recieve
// read mode
byte[] modeBytes = new byte[5];
// String mode =
String.valueOf(in.read(modeBytes, 0, 5));
// read file size
StringBuilder fileSizeString = new StringBuilder();
char readedChar = (char) in.read();
while (' ' != readedChar) {
fileSizeString.append(readedChar);
readedChar = (char) in.read();
}
long fileSize = Long.parseLong(fileSizeString.toString());
// read file name
StringBuilder fileNameStringBuffer = new StringBuilder();
readedChar = (char) in.read();
while (0x0a != (byte) readedChar) {
fileNameStringBuffer.append(readedChar);
readedChar = (char) in.read();
}
String fileName = fileNameStringBuffer.toString();
// send readed sign
out.write(new byte[1]);
out.flush();
outputDirectory.mkdirs();
File outFile = new File(outputDirectory, fileName);
FileOutputStream fout = new FileOutputStream(outFile);
IOUtil.copy(in, fout, fileSize);
fout.close();
checkAck(in);
// send readed sign
out.write(new byte[1]);
out.flush();
}
out.close();
channel.disconnect();
} catch (JSchException e) {
throw new IOException(e);
} finally {
if (channel != null) {
channel.disconnect();
}
}
}
public void replaceLine(String remoteFilePath, String replaceFrom, String replaceTo) throws IOException {
replaceLine(remoteFilePath, replaceFrom, replaceTo, encode);
}
public void replaceLine(String remoteFilePath, String replaceFrom, String replaceTo, String encode) throws IOException {
String localTmpDirName = UUID.randomUUID().toString();
File tmpDir = new File(localTmpDirName);
tmpDir.mkdirs();
this.recieve(remoteFilePath, tmpDir);
DirectoryPartAndFileName dpafn = FileUtil.dividePathToParentDirectoryAndFileName(remoteFilePath);
File targetFile = new File(tmpDir, dpafn.getFileName());
new Local(baseDir, runtime).replaceLine(targetFile, replaceFrom, replaceTo, encode);
this.send(targetFile, dpafn.getDirectoryPart(), dpafn.getFileName());
new Local(baseDir, runtime).remove(tmpDir);
}
public void comment(String remoteFilePath, String target) throws IOException {
comment(remoteFilePath, target, encode);
}
public void comment(String remoteFilePath, String target, String encode) throws IOException {
String localTmpDirName = UUID.randomUUID().toString();
File tmpDir = new File(localTmpDirName);
tmpDir.mkdirs();
this.recieve(remoteFilePath, tmpDir);
DirectoryPartAndFileName dpafn = FileUtil.dividePathToParentDirectoryAndFileName(remoteFilePath);
File targetFile = new File(tmpDir, dpafn.getFileName());
new Local(baseDir, runtime).comment(targetFile, target, encode);
this.send(targetFile, dpafn.getDirectoryPart(), dpafn.getFileName());
new Local(baseDir, runtime).remove(tmpDir);
}
public void uncomment(String remoteFilePath, String target) throws IOException {
uncomment(remoteFilePath, target, encode);
}
public void uncomment(String remoteFilePath, String target, String encode) throws IOException {
String localTmpDirName = UUID.randomUUID().toString();
File tmpDir = new File(localTmpDirName);
tmpDir.mkdirs();
this.recieve(remoteFilePath, tmpDir);
DirectoryPartAndFileName dpafn = FileUtil.dividePathToParentDirectoryAndFileName(remoteFilePath);
File targetFile = new File(tmpDir, dpafn.getFileName());
new Local(baseDir, runtime).uncomment(targetFile, target, encode);
this.send(targetFile, dpafn.getDirectoryPart(), dpafn.getFileName());
new Local(baseDir, runtime).remove(tmpDir);
}
public Shell startShell() throws IOException {
if (null == this.session) {
throw new RuntimeException("connect before start shell");
}
try {
ChannelShell channel = (ChannelShell) this.session.openChannel("shell");
Shell shell = new Shell(channel);
shell.read();
return shell;
} catch (JSchException e) {
throw new IOException(e);
}
}
public Shell startShell(int readWaitTime) throws IOException {
if (null == this.session) {
throw new RuntimeException("connect before start shell");
}
try {
ChannelShell channel = (ChannelShell) this.session.openChannel("shell");
Shell shell = new Shell(channel, readWaitTime);
return shell;
} catch (JSchException e) {
throw new IOException(e);
}
}
public class Shell {
private final ChannelShell channel;
private final LinkedListInputStream in;
private final OutputStream out;
private String encode = "UTF-8";
private String lang = "ja_JP.UTF-8";
private int readWaitTime = 1000;
public Shell(ChannelShell channel) throws IOException {
this(channel, 1000);
}
public Shell(ChannelShell channel, int readWaitTime) throws IOException {
this.readWaitTime = readWaitTime;
this.channel = channel;
final LinkedList<Integer> outToIn = new LinkedList<Integer>();
final LinkedList<Integer> inToOut = new LinkedList<Integer>();
this.in = new LinkedListInputStream(inToOut, readWaitTime);
this.out = new LinkedListOutputStream(outToIn);
this.channel.setInputStream(new LinkedListInputStream(outToIn));
this.channel.setOutputStream(new LinkedListOutputStream(inToOut));
this.channel.setEnv("LANG", lang);
try {
this.channel.connect();
} catch (JSchException e) {
throw new IOException(e);
}
}
public void setReadWaitTime(int readWaitTime){
this.in.setReadTimeout(readWaitTime);
}
public void call(String command) throws IOException {
Remote.this.out.println(getPromptCharacter() + command);
Remote.this.out.println(execute(command));
}
@SuppressWarnings("unused")
public String execute(String command) throws IOException {
byte[] sendCommandBytes = (command + "\n").getBytes(encode);
this.out.write(sendCommandBytes);
this.out.write(new byte[1]);
this.out.flush();
byte[] commandResponseResultByte = new byte[sendCommandBytes.length];
read(commandResponseResultByte);
String response = new String(commandResponseResultByte, encode);
if (true || response.replace("\r", "").replace("\n", "").replace("&", "").trim().equals(command.replace("\r", "").replace("\n", "").replace("&", "").trim())) {// pass
// check
// caz
// response
// is
// unstable
return read();
}
throw new IOException("response bloken[" + response.replace("\r", "\\r").replace("\n", "\\n") + "] expect [" + (command + "\n").replace("\r", "\\r").replace("\n", "\\n") + "]");
}
public void read(byte[] buffer) throws IOException {
in.read(buffer);
}
public String read() throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
int arg = this.in.read();
while (-1 != arg && LinkedListInputStream.RETURN_AS_TIMEOUT != arg) {
bout.write(arg);
arg = this.in.read();
}
String returnArg = new String(bout.toByteArray(), encode);
bout.reset();
return returnArg;
}
public void close() throws IOException {
in.close();
out.close();
channel.disconnect();
}
public int getReadWaitTime() {
return readWaitTime;
}
public InputStream getIn() {
return this.in;
}
public String getEncode() {
return encode;
}
public void setEncode(String encode) {
this.encode = encode;
}
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
}
public void call(String command) throws IOException {
out.println(getPromptCharacter() + command);
out.println(execute(command));
}
public String execute(String command) throws IOException {
return execute(command, null);
}
public String execute(String command, MessageCallback messageCallback) throws IOException {
ChannelExec channel = null;
try {
channel = (ChannelExec) session.openChannel("exec");
channel.setPty(true);
channel.setCommand(command);
channel.connect();
InputStream in = channel.getInputStream();
byte[] tmp = new byte[1024];
ByteArrayOutputStream bout = new ByteArrayOutputStream();
for (int readed; (readed = in.read(tmp)) >= 0 && !channel.isClosed();) {
bout.write(tmp, 0, readed);
if (null != messageCallback) {
messageCallback.onMessage(new String(tmp, 0, readed, getEncode()));
}
}
byte[] stringSrc = bout.toByteArray();
if (0 == stringSrc.length) {
return "";
}
return new String(stringSrc, 0, stringSrc.length - 1, getEncode());
} catch (JSchException e) {
throw new IOException(e);
} finally {
if (channel != null) {
channel.disconnect();
}
}
}
public void close() {
this.session.disconnect();
}
private boolean checkAck(InputStream in) throws IOException {
return checkAck(in, 0, true);
}
private boolean checkAck(InputStream in, int shudbe, boolean throwExceptionIfNot) throws IOException {
int b = in.read();
// b may be 0 for success,
// 1 for error,
// 2 for fatal error,
// -1
if (b == shudbe) {
return true;
}
if (b == -1) {
if (throwExceptionIfNot) {
throw new AckException("unknown", b);
}
return false;
}
StringBuffer sb = new StringBuffer();
int c;
do {
c = in.read();
sb.append((char) c);
} while (c != '\n');
if (throwExceptionIfNot) {
throw new AckException(sb.toString(), b);
} else {
return false;
}
}
public String getEncode() {
return encode;
}
public void setEncode(String encode) {
this.encode = encode;
}
public String getHost() {
return host;
}
public String getPromptCharacter() {
return promptCharacter;
}
public void setPromptCharacter(String promptCharacter) {
this.promptCharacter = promptCharacter;
}
public static interface MessageCallback {
public void onMessage(String message);
}
public static class AckException extends IOException {
private int status;
public AckException(String message, int status) {
super(message);
this.status = status;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
}